package club.gskj.gulimall.search.service.impl;

import club.gskj.common.to.es.SkuEsModel;
import club.gskj.common.utils.R;
import club.gskj.gulimall.search.config.GulimallElasticSearchConfig;
import club.gskj.gulimall.search.constant.EsConstant;
import club.gskj.gulimall.search.feign.ProductFeignService;
import club.gskj.gulimall.search.service.IMallSearchService;
import club.gskj.gulimall.search.vo.AttrResponseVo;
import club.gskj.gulimall.search.vo.BrandVo;
import club.gskj.gulimall.search.vo.SearchParamVo;
import club.gskj.gulimall.search.vo.SearchResult;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import lombok.val;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.*;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @ClassName MallSearchServiceImpl
 * @Description TODO
 * @Author 康纳
 * @Date 2021-02-27
 */


@Service
public class MallSearchServiceImpl implements IMallSearchService {

    /**
     * ES检索 客户端
     */
    @Autowired
    private RestHighLevelClient client;

    /**
     * 远程调用服务接口
     */
    @Autowired
    private ProductFeignService productFeignService;

    @Override
    public SearchResult search(SearchParamVo paramVo) {
        // 1.动态构建查询需要的检索 DSL语句
        SearchResult result = null;

        // 2.准备检索请求
        SearchRequest searchRequest = buildSearchRequest(paramVo);
        try {
            SearchResponse response = client.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);

            // 3.分析响应数据封装并返回
            result = buildSearchResult(response, paramVo);

        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

    /***
     *
     * @param response
     * @return club.gskj.gulimall.search.vo.SearchResult
     * @author 康纳-connor
     * @creed: Talk is cheap,show me the code
     * @date 21.3.2 22:13
     * @Description 构建es响应结果数据，并封装为指定格式返回
     */
    private SearchResult buildSearchResult(SearchResponse response, SearchParamVo paramVo) {

        SearchResult result = new SearchResult();

        // 1. 查询的所有sku商品
        SearchHits hits = response.getHits();

        List<SkuEsModel> esModelList = new ArrayList<>();
        if (hits.getHits() != null && hits.getHits().length > 0) {

            for (SearchHit hit : hits.getHits()) {
                String sourceAsString = hit.getSourceAsString();
                // 商品信息
                SkuEsModel skuEsModel1 = JSON.parseObject(sourceAsString, SkuEsModel.class);

                // 判断是否有高亮信息
                if (!StringUtils.isEmpty(paramVo.getKeyword())) {
                    HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
                    String s = skuTitle.getFragments()[0].string();
                    skuEsModel1.setSkuTitle(s);
                }
                esModelList.add(skuEsModel1);
            }
        }
        result.setProducts(esModelList);

        // 2. 当前商品涉及的属性聚合信息
        List<SearchResult.AttrVo> attrVoList = new ArrayList<>();
        //   获取属性 agg
        ParsedNested attr_agg = response.getAggregations().get("attr_agg");
        // 获取属性id 的buchets
        ParsedLongTerms attr_id_agg = attr_agg.getAggregations().get("attr_id_agg");

        for (Terms.Bucket bucket : attr_id_agg.getBuckets()) {
            SearchResult.AttrVo attrVo = new SearchResult.AttrVo();

            // 属性id
            Long attrId = bucket.getKeyAsNumber().longValue();
            // 属性名称
            String attrName = ((ParsedStringTerms) bucket.getAggregations().get("attr_name_agg")).getBuckets().get(0).getKeyAsString();
            // 属性值
            List<String> attValues = ((ParsedStringTerms) bucket.getAggregations().get("attr_value_agg")).getBuckets().stream().map(item -> {
                String attrValue = ((Terms.Bucket) item).getKeyAsString();
                return attrValue;
            }).collect(Collectors.toList());
            attrVo.setAttrId(attrId);
            attrVo.setAttrName(attrName);
            attrVo.setAttrValue(attValues);

            attrVoList.add(attrVo);
        }
        result.setAttrVos(attrVoList);

        // 3. 当前商品涉及的品牌聚合信息
        List<SearchResult.BrandVo> brandVoList = new ArrayList<>();
        ParsedLongTerms brand_agg = response.getAggregations().get("brand_agg");
        for (Terms.Bucket bucket : brand_agg.getBuckets()) {
            SearchResult.BrandVo brandVo = new SearchResult.BrandVo();

            // 品牌id
            Long brandId = bucket.getKeyAsNumber().longValue();
            brandVo.setBrandId(brandId);

            // 品牌名称
            String brandName = ((ParsedStringTerms) bucket.getAggregations().get("brand_name_agg")).getBuckets().get(0).getKeyAsString();
            brandVo.setBrandName(brandName);

            // 品牌logo
            String brandLogo = ((ParsedStringTerms) bucket.getAggregations().get("brand_logo_agg")).getBuckets().get(0).getKeyAsString();
            brandVo.setLogo(brandLogo);

            brandVoList.add(brandVo);
        }
        result.setBrandVos(brandVoList);

        // 4. 当前商品涉及的分类聚合信息
        List<SearchResult.catelogVo> catelogVoList = new ArrayList<>();
        ParsedLongTerms catalog_agg = response.getAggregations().get("catalog_agg");
        List<? extends Terms.Bucket> buckets = catalog_agg.getBuckets();
        for (Terms.Bucket bucket : buckets) {
            // 聚合分类信息
            SearchResult.catelogVo catelogVo = new SearchResult.catelogVo();
            // 分类id
            String keyAsString = bucket.getKeyAsString();
            catelogVo.setCatelogId(Long.valueOf(keyAsString));
            // 从子聚合中 获取 分类id对应的 分类名称
            ParsedStringTerms catelog_name_agg = bucket.getAggregations().get("catelog_name_agg");
            String catelogName = catelog_name_agg.getBuckets().get(0).getKeyAsString();
            catelogVo.setCatelogName(catelogName);
            catelogVoList.add(catelogVo);
        }
        result.setCatelogVos(catelogVoList);
        // =============以上从聚合信息中获取=====================

        // 5. 分页信息 页码
        Integer pageNum = paramVo.getPageNum();
        result.setPageNum(pageNum);

        // 5. 分页信息 总记录数
        long total = hits.getTotalHits().value;
        result.setTotal(total);

        // 5. 分页信息 总页码
        // 计算得到
        int totalPage = (int) total % EsConstant.PRODUDT_PAGESIZE == 0 ? (int) total / EsConstant.PRODUDT_PAGESIZE : ((int) total / EsConstant.PRODUDT_PAGESIZE) + 1;
        result.setTotalPages(totalPage);
        // 封装 页码数组
        List<Integer> pageNavs = new ArrayList<>();
        for (int i = 1; i <= totalPage; i++) {
            pageNavs.add(i);
        }
        result.setPageNavs(pageNavs);

        // 6. 构建面包屑导航功能
        if (paramVo.getAttrs() != null && paramVo.getAttrs().size() > 0) {
            List<SearchResult.NavVo> collect = paramVo.getAttrs().stream().map(item -> {
                SearchResult.NavVo navVo = new SearchResult.NavVo();
                // attrs=1_IOS:安卓:微软&attrs=2_16G:32G
                String[] s = item.split("_");
                navVo.setNavValue(s[1]);

                String attrId = s[0];
                // 保存已经筛选的attrId
                result.getAttrIds().add(Long.valueOf(attrId));

                // 调用 商品服务远程接口 查询name
                R attrInfo = productFeignService.getAttrInfo(Long.valueOf(attrId));
                if (attrInfo.getCode() == 0) {
                    AttrResponseVo attr = attrInfo.getData("attr", new TypeReference<AttrResponseVo>() {
                    });
                    navVo.setNavName(attr.getAttrName());
                } else {
                    // 远程服务接口调用失败
                    navVo.setNavName(s[0]);
                }
                // 调用方法，替换查询url 参数值
                String replace = replaceQueryString(paramVo, item, "attrs");

                navVo.setLink("http://search.gulimall.com/list.html?" + replace);
                return navVo;
            }).collect(Collectors.toList());
            result.setNavs(collect);
        }
        // 品牌、分类
        if (paramVo.getBrandId() != null && paramVo.getBrandId().size() > 0) {
            List<SearchResult.NavVo> navs = result.getNavs();
            SearchResult.NavVo navVo = new SearchResult.NavVo();
            navVo.setNavName("品牌");
            // 远程调用查询品牌信息
            R r = productFeignService.brandInfos(paramVo.getBrandId());
            if (r.getCode() == 0) {
                // 获取id指定的品牌信息
                List<BrandVo> brands = r.getData("brands", new TypeReference<List<BrandVo>>() {
                });
                StringBuffer stringBuffer = new StringBuffer();
                String replace = null;
                for (BrandVo brand : brands) {
                    // url 替换
                    replace = replaceQueryString(paramVo, brand.getBrandId().toString(), "brandId");
                    stringBuffer.append(brand.getName() + ";");
                }
                navVo.setNavValue(stringBuffer.toString());
                navVo.setLink("http://search.gulimall.com/list.html?" + replace);
            }
            navs.add(navVo);
        }

        // TOOD 分类，不需要导航取消;

        return result;
    }


    /**
     * @param paramVo 查询条件
     * @param value   要替换的值 (xx=15_ios14)
     * @param key     要替换的键 (attrs=xxxx_xx)
     * @return java.lang.String
     * @author 康纳-connor
     * @creed: Talk is cheap,show me the code
     * @date 21.3.9 21:59
     * @Description
     */
    private String replaceQueryString(SearchParamVo paramVo, String value, String key) {
        // 将取消面包屑以后的请求地址中url置空
        // 拿到所有查询条件，去掉当前
        // attrs= 15_骁龙
        // 默认请求地址会中文乱码
        String encode = null;
        try {
            encode = URLEncoder.encode(value, "UTF-8");
            // 针对浏览器 处理空格的差异 空格默认转为 +
            encode = encode.replace("+", "%20");
            encode = encode.replace("%3B", ";");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        String replace = paramVo.get_queryString().replace("&" + key + "=" + encode, "");
        return replace;
    }

    /**
     * # 模糊匹配、过滤(属性、分类、品牌、价格区间、库存)、排序、分页、指定字段高亮、聚合分析查询
     *
     * @param
     * @return org.elasticsearch.action.search.SearchRequest
     * @author 康纳-connor
     * @creed: Talk is cheap,show me the code
     * @date 21.3.2 22:12
     * @Description 构建es检索请求信息
     */
    private SearchRequest buildSearchRequest(SearchParamVo paramVo) {
        // 用于构建DSL语句
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        /**
         *    查询：过滤(属性、分类、品牌、价格区间、库存)
         */
        // 1. 构建bool query
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        // 1.1 must 模糊匹配、
        if (!StringUtils.isEmpty(paramVo.getKeyword())) {

            boolQuery.must(QueryBuilders.matchQuery("skuTitle", paramVo.getKeyword()));
        }

        //1.2 bool- filter 按照 分类id查询
        if (paramVo.getCatalog3Id() != null) {
            boolQuery.filter(QueryBuilders.termQuery("catelogId", paramVo.getCatalog3Id()));
        }

        //1.2 bool- filter  按照品牌id查询
        if (paramVo.getBrandId() != null && paramVo.getBrandId().size() > 0) {
            boolQuery.filter(QueryBuilders.termsQuery("brandId", paramVo.getBrandId()));
        }

        //1.2 bool- filter 按照是否有库存查询
        if (paramVo.getHasStock() != null) {
            boolQuery.filter(QueryBuilders.termsQuery("hasStock", paramVo.getHasStock() == 1));
        }

        //1.2 bool- filter 按照 价格区间查询
        String skuPriceStr = paramVo.getSkuPrice();
        if (!StringUtils.isEmpty(skuPriceStr)) {
            // skuPrice=1_500,_500,500_
            /**
             *           "range": {
             *             "skuPrice": {
             *               "gte": 0,
             *               "lte": 6000
             *             }
             *           }
             */
            RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");
            // 分割处理
            String[] skuPriceArr = skuPriceStr.split("_");
            if (skuPriceArr.length == 2) {
                // 拼接区间 大于等于gte 小于等于lte
                rangeQuery
                        .gte(skuPriceArr[0])
                        .lte(skuPriceArr[1]);
            } else if (skuPriceArr.length == 1) {
                // 判断 _在前为 大于， 在后为小于
                if (skuPriceStr.startsWith("_")) {
                    rangeQuery.lte(skuPriceArr[0]);
                }
                if (skuPriceStr.endsWith("_")) {
                    rangeQuery.gte(skuPriceArr[0]);
                }
            }
            boolQuery.filter(rangeQuery);
        }

        //1.2 bool- filter 按照 属性查询
        if (paramVo.getAttrs() != null && paramVo.getAttrs().size() > 0) {

            // attrs=1_IOS:安卓:微软&attrs=2_16G:32G
            for (String attrStr : paramVo.getAttrs()) {
                BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
                // attrs=2_16G:32G
                String[] s = attrStr.split("_");
                // 属性id
                String attrId = s[0];
                // 属性结果,属性检索用的值
                String[] attrVals = s[1].split(":");

                nestedBoolQuery.must(QueryBuilders.termsQuery("attrs.attrId", attrId));
                nestedBoolQuery.must(QueryBuilders.termsQuery("attrs.attrValue", attrVals));
                // 每一遍历都必须生成一个nested查询
                NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestedBoolQuery, ScoreMode.None);
                boolQuery.filter(nestedQuery);
            }
        }

        // 把所有查询条件都拿来进行封装查询
        searchSourceBuilder.query(boolQuery);

        /***
         * 排序，分页，高亮
         */

        //2.1 排序
        if (!StringUtils.isEmpty(paramVo.getSort())) {
            // saleCount_asc/desc
            String sortStr = paramVo.getSort();
            String[] s = sortStr.split("_");

            SortOrder order = s[1].equalsIgnoreCase("asc") ? SortOrder.ASC : SortOrder.DESC;
            searchSourceBuilder.sort(s[0], order);
        }

        //2.2 分页
        // 页码 pagenum 1 from 0 pagesize 5
        // 页码 pagenum 2 from 5 pagesize 5
        // 页码 pagenum 3 from 10 pagesize 5

        // from = pagenum-1 * pagesize
        searchSourceBuilder.from((paramVo.getPageNum() - 1) * EsConstant.PRODUDT_PAGESIZE);
        // 每页显示条数
        searchSourceBuilder.size(EsConstant.PRODUDT_PAGESIZE);

        // 2.3 高亮显示，关键字
        // 只有请求参数携带了 关键字查询，才启动高亮功能
        if (!StringUtils.isEmpty(paramVo.getKeyword())) {
            HighlightBuilder highlightBuilder = new HighlightBuilder();

            highlightBuilder.field("skuTitle");
            highlightBuilder.preTags("<b style='color:red'>");
            highlightBuilder.postTags("</b>");

            searchSourceBuilder.highlighter(highlightBuilder);
        }

        /**
         *  3. 聚合分析
         */
        // 1. 品牌聚合
        TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg");
        brand_agg.field("brandId").size(50);

        // 3.1 品牌聚合的子聚合
        // 品牌名称
        // TODO 聚合品牌信息
        brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(1));
        // 品牌图片
        brand_agg.subAggregation(AggregationBuilders.terms("brand_logo_agg").field("brandImg").size(1));
        searchSourceBuilder.aggregation(brand_agg);

        // 3.2 分类聚合
        // TODO 聚合分类信息
        TermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg").field("catelogId").size(20);
        // 分类子聚合
        catalog_agg.subAggregation(AggregationBuilders.terms("catelog_name_agg").field("catelogName").size(1));
        searchSourceBuilder.aggregation(catalog_agg);

        // 3.3 属性聚合(所有的 attr_id)
        // TODO 聚合属性信息
        NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs");
        // 嵌入式聚合(扁平化处理)
        TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId");
        // 子聚合(分析attr_id 对应的 attr_name)
        TermsAggregationBuilder attr_name_agg = AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1);
        attr_id_agg.subAggregation(attr_name_agg);
        // 子聚合(分析attr_id 对应的所有 attr_value)
        TermsAggregationBuilder attr_value_agg = AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(1);
        attr_id_agg.subAggregation(attr_value_agg);

        attr_agg.subAggregation(attr_id_agg);
        searchSourceBuilder.aggregation(attr_agg);

        String dsl = searchSourceBuilder.toString();
        System.out.println("构建的DSL " + dsl);

        SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, searchSourceBuilder);
        return searchRequest;
    }
}
